{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# default_exp data.preprocessing"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Data preprocessing\n",
    "\n",
    "> Functions used to preprocess time series (both X and y)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "from tsai.imports import *\n",
    "from tsai.utils import *\n",
    "from tsai.data.external import *\n",
    "from tsai.data.core import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dsid = 'NATOPS'\n",
    "X, y, splits = get_UCR_data(dsid, return_split=False)\n",
    "tfms = [None, Categorize()]\n",
    "dsets = TSDatasets(X, y, tfms=tfms, splits=splits)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class ToNumpyCategory(Transform):\n",
    "    \"Categorize a numpy batch\"\n",
    "    order = 90\n",
    "\n",
    "    def __init__(self, **kwargs):\n",
    "        super().__init__(**kwargs)\n",
    "\n",
    "    def encodes(self, o: np.ndarray):\n",
    "        self.type = type(o)\n",
    "        self.cat = Categorize()\n",
    "        self.cat.setup(o)\n",
    "        self.vocab = self.cat.vocab\n",
    "        return np.asarray(stack([self.cat(oi) for oi in o]))\n",
    "\n",
    "    def decodes(self, o: (np.ndarray, torch.Tensor)):\n",
    "        return stack([self.cat.decode(oi) for oi in o])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([3, 2, 2, 3, 2, 4, 0, 5, 2, 1])"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = ToNumpyCategory()\n",
    "y_cat = t(y)\n",
    "y_cat[:10]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(t.decode(tensor(y_cat)), y)\n",
    "test_eq(t.decode(np.array(y_cat)), y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class OneHot(Transform): \n",
    "    \"One-hot encode/ decode a batch\"\n",
    "    order = 90\n",
    "    def __init__(self, n_classes=None, **kwargs): \n",
    "        self.n_classes = n_classes\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: torch.Tensor): \n",
    "        if not self.n_classes: self.n_classes = len(np.unique(o))\n",
    "        return torch.eye(self.n_classes)[o]\n",
    "    def encodes(self, o: np.ndarray): \n",
    "        o = ToNumpyCategory()(o)\n",
    "        if not self.n_classes: self.n_classes = len(np.unique(o))\n",
    "        return np.eye(self.n_classes)[o]\n",
    "    def decodes(self, o: torch.Tensor): return torch.argmax(o, dim=-1)\n",
    "    def decodes(self, o: np.ndarray): return np.argmax(o, axis=-1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0., 0., 0., 1., 0., 0.],\n",
       "       [0., 0., 1., 0., 0., 0.],\n",
       "       [0., 0., 1., 0., 0., 0.],\n",
       "       [0., 0., 0., 1., 0., 0.],\n",
       "       [0., 0., 1., 0., 0., 0.],\n",
       "       [0., 0., 0., 0., 1., 0.],\n",
       "       [1., 0., 0., 0., 0., 0.],\n",
       "       [0., 0., 0., 0., 0., 1.],\n",
       "       [0., 0., 1., 0., 0., 0.],\n",
       "       [0., 1., 0., 0., 0., 0.]])"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "oh_encoder = OneHot()\n",
    "y_cat = ToNumpyCategory()(y)\n",
    "oht = oh_encoder(y_cat)\n",
    "oht[:10]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_classes = 10\n",
    "n_samples = 100\n",
    "\n",
    "t = torch.randint(0, n_classes, (n_samples,))\n",
    "oh_encoder = OneHot()\n",
    "oht = oh_encoder(t)\n",
    "test_eq(oht.shape, (n_samples, n_classes))\n",
    "test_eq(torch.argmax(oht, dim=-1), t)\n",
    "test_eq(oh_encoder.decode(oht), t)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_classes = 10\n",
    "n_samples = 100\n",
    "\n",
    "a = np.random.randint(0, n_classes, (n_samples,))\n",
    "oh_encoder = OneHot()\n",
    "oha = oh_encoder(a)\n",
    "test_eq(oha.shape, (n_samples, n_classes))\n",
    "test_eq(np.argmax(oha, axis=-1), a)\n",
    "test_eq(oh_encoder.decode(oha), a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class Nan2Value(Transform):\n",
    "    \"Replaces any nan values by a predefined value or median\"\n",
    "    order = 90\n",
    "    def __init__(self, value=0, median=False, by_sample_and_var=True):\n",
    "        store_attr()\n",
    "    def encodes(self, o:TSTensor):\n",
    "        mask = torch.isnan(o)\n",
    "        if mask.any():\n",
    "            if self.median:\n",
    "                if self.by_sample_and_var:\n",
    "                    median = torch.nanmedian(o, dim=2, keepdim=True)[0].repeat(1, 1, o.shape[-1])\n",
    "                    o[mask] = median[mask]\n",
    "                else:\n",
    "#                     o = torch.nan_to_num(o, torch.nanmedian(o)) # Only available in Pytorch 1.8\n",
    "                    o = torch_nan_to_num(o, torch.nanmedian(o))\n",
    "#             o = torch.nan_to_num(o, self.value) # Only available in Pytorch 1.8\n",
    "        o = torch_nan_to_num(o, self.value)   \n",
    "        return o"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "o = TSTensor(torch.randn(16, 10, 100))\n",
    "o[0,0] = float('nan')\n",
    "o[o > .9] = float('nan')\n",
    "o[[0,1,5,8,14,15], :, -20:] = float('nan')\n",
    "nan_vals1 = torch.isnan(o).sum()\n",
    "o2 = Pipeline(Nan2Value(), split_idx=0)(o.clone())\n",
    "o3 = Pipeline(Nan2Value(median=True, by_sample_and_var=True), split_idx=0)(o.clone())\n",
    "o4 = Pipeline(Nan2Value(median=True, by_sample_and_var=False), split_idx=0)(o.clone())\n",
    "nan_vals2 = torch.isnan(o2).sum()\n",
    "nan_vals3 = torch.isnan(o3).sum()\n",
    "nan_vals4 = torch.isnan(o4).sum()\n",
    "test_ne(nan_vals1, 0)\n",
    "test_eq(nan_vals2, 0)\n",
    "test_eq(nan_vals3, 0)\n",
    "test_eq(nan_vals4, 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# export\n",
    "\n",
    "class TSStandardize(Transform):\n",
    "    \"\"\"Standardizes batch of type `TSTensor`\n",
    "\n",
    "    Args:\n",
    "        - mean: you can pass a precalculated mean value as a torch tensor which is the one that will be used, or leave as None, in which case\n",
    "            it will be estimated using a batch.\n",
    "        - std: you can pass a precalculated std value as a torch tensor which is the one that will be used, or leave as None, in which case\n",
    "            it will be estimated using a batch. If both mean and std values are passed when instantiating TSStandardize, the rest of arguments won't be used.\n",
    "        - by_sample: if True, it will calculate mean and std for each individual sample. Otherwise based on the entire batch.\n",
    "        - by_var:\n",
    "            * False: mean and std will be the same for all variables.\n",
    "            * True: a mean and std will be be different for each variable.\n",
    "            * a list of ints: (like [0,1,3]) a different mean and std will be set for each variable on the list. Variables not included in the list\n",
    "            won't be standardized.\n",
    "            * a list that contains a list/lists: (like[0, [1,3]]) a different mean and std will be set for each element of the list. If multiple elements are\n",
    "            included in a list, the same mean and std will be set for those variable in the sublist/s. (in the example a mean and std is determined for\n",
    "            variable 0, and another one for variables 1 & 3 - the same one). Variables not included in the list won't be standardized.\n",
    "        - by_step: if False, it will standardize values for each time step.\n",
    "        - eps: it avoids dividing by 0\n",
    "        - use_single_batch: if True a single training batch will be used to calculate mean & std. Else the entire training set will be used.\n",
    "    \"\"\"\n",
    "\n",
    "    parameters, order = L('mean', 'std'), 90\n",
    "    _setup = True # indicates it requires set up\n",
    "    def __init__(self, mean=None, std=None, by_sample=False, by_var=False, by_step=False, eps=1e-8, use_single_batch=True, verbose=False):\n",
    "        self.mean = tensor(mean) if mean is not None else None\n",
    "        self.std = tensor(std) if std is not None else None\n",
    "        self._setup = (mean is None or std is None) and not by_sample\n",
    "        self.eps = eps\n",
    "        self.by_sample, self.by_var, self.by_step = by_sample, by_var, by_step\n",
    "        drop_axes = []\n",
    "        if by_sample: drop_axes.append(0)\n",
    "        if by_var: drop_axes.append(1)\n",
    "        if by_step: drop_axes.append(2)\n",
    "        self.axes = tuple([ax for ax in (0, 1, 2) if ax not in drop_axes])\n",
    "        if by_var and is_listy(by_var):\n",
    "            self.list_axes = tuple([ax for ax in (0, 1, 2) if ax not in drop_axes]) + (1,)\n",
    "        self.use_single_batch = use_single_batch\n",
    "        self.verbose = verbose\n",
    "        if self.mean is not None or self.std is not None:\n",
    "            pv(f'{self.__class__.__name__} mean={self.mean}, std={self.std}, by_sample={self.by_sample}, by_var={self.by_var}, by_step={self.by_step}\\n', self.verbose)\n",
    "\n",
    "    @classmethod\n",
    "    def from_stats(cls, mean, std): return cls(mean, std)\n",
    "\n",
    "    def setups(self, dl: DataLoader):\n",
    "        if self._setup:\n",
    "            if not self.use_single_batch:\n",
    "                o = dl.dataset.__getitem__([slice(None)])[0]\n",
    "            else:\n",
    "                o, *_ = dl.one_batch()\n",
    "            if self.by_var and is_listy(self.by_var):\n",
    "                shape = torch.mean(o, dim=self.axes, keepdim=self.axes!=()).shape\n",
    "                mean = torch.zeros(*shape, device=o.device)\n",
    "                std = torch.ones(*shape, device=o.device)\n",
    "                for v in self.by_var:\n",
    "                    if not is_listy(v): v = [v]\n",
    "                    mean[:, v] = torch_nanmean(o[:, v], dim=self.axes if len(v) == 1 else self.list_axes, keepdim=True)\n",
    "                    std[:, v] = torch.clamp_min(torch_nanstd(o[:, v], dim=self.axes if len(v) == 1 else self.list_axes, keepdim=True), self.eps)\n",
    "            else:\n",
    "                mean = torch_nanmean(o, dim=self.axes, keepdim=self.axes!=())\n",
    "                std = torch.clamp_min(torch_nanstd(o, dim=self.axes, keepdim=self.axes!=()), self.eps)\n",
    "            self.mean, self.std = mean, std\n",
    "            if len(self.mean.shape) == 0:\n",
    "                pv(f'{self.__class__.__name__} mean={self.mean}, std={self.std}, by_sample={self.by_sample}, by_var={self.by_var}, by_step={self.by_step}\\n',\n",
    "                   self.verbose)\n",
    "            else:\n",
    "                pv(f'{self.__class__.__name__} mean shape={self.mean.shape}, std shape={self.std.shape}, by_sample={self.by_sample}, by_var={self.by_var}, by_step={self.by_step}\\n',\n",
    "                   self.verbose)\n",
    "            self._setup = False\n",
    "        elif self.by_sample: self.mean, self.std = torch.zeros(1), torch.ones(1)\n",
    "\n",
    "    def encodes(self, o:TSTensor):\n",
    "        if self.by_sample:\n",
    "            if self.by_var and is_listy(self.by_var):\n",
    "                shape = torch.mean(o, dim=self.axes, keepdim=self.axes!=()).shape\n",
    "                mean = torch.zeros(*shape, device=o.device)\n",
    "                std = torch.ones(*shape, device=o.device)\n",
    "                for v in self.by_var:\n",
    "                    if not is_listy(v): v = [v]\n",
    "                    mean[:, v] = torch_nanmean(o[:, v], dim=self.axes if len(v) == 1 else self.list_axes, keepdim=True)\n",
    "                    std[:, v] = torch.clamp_min(torch_nanstd(o[:, v], dim=self.axes if len(v) == 1 else self.list_axes, keepdim=True), self.eps)\n",
    "            else:\n",
    "                mean = torch_nanmean(o, dim=self.axes, keepdim=self.axes!=())\n",
    "                std = torch.clamp_min(torch_nanstd(o, dim=self.axes, keepdim=self.axes!=()), self.eps)\n",
    "            self.mean, self.std = mean, std\n",
    "        return (o - self.mean) / self.std\n",
    "\n",
    "    def decodes(self, o:TSTensor):\n",
    "        if self.mean is None or self.std is None: return o\n",
    "        return o * self.std + self.mean\n",
    "\n",
    "    def __repr__(self): return f'{self.__class__.__name__}(by_sample={self.by_sample}, by_var={self.by_var}, by_step={self.by_step})'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "batch_tfms=[TSStandardize(by_sample=True, by_var=False, verbose=True)]\n",
    "dls = TSDataLoaders.from_dsets(dsets.train, dsets.valid, bs=128, num_workers=0, batch_tfms=batch_tfms)\n",
    "xb, yb = next(iter(dls.train))\n",
    "test_close(xb.mean(), 0, eps=1e-1)\n",
    "test_close(xb.std(), 1, eps=1e-1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from tsai.data.validation import TimeSplitter\n",
    "X_nan = np.random.rand(100, 5, 10)\n",
    "idxs = np.random.choice(len(X_nan), int(len(X_nan)*.5), False)\n",
    "X_nan[idxs, 0] = float('nan')\n",
    "idxs = np.random.choice(len(X_nan), int(len(X_nan)*.5), False)\n",
    "X_nan[idxs, 1, -10:] = float('nan')\n",
    "batch_tfms = TSStandardize(by_var=True)\n",
    "dls = get_ts_dls(X_nan, batch_tfms=batch_tfms, splits=TimeSplitter(show_plot=False)(range_of(X_nan)))\n",
    "test_eq(torch.isnan(dls.after_batch[0].mean).sum(), 0)\n",
    "test_eq(torch.isnan(dls.after_batch[0].std).sum(), 0)\n",
    "xb = first(dls.train)[0]\n",
    "test_ne(torch.isnan(xb).sum(), 0)\n",
    "test_ne(torch.isnan(xb).sum(), torch.isnan(xb).numel())\n",
    "batch_tfms = [TSStandardize(by_var=True), Nan2Value()]\n",
    "dls = get_ts_dls(X_nan, batch_tfms=batch_tfms, splits=TimeSplitter(show_plot=False)(range_of(X_nan)))\n",
    "xb = first(dls.train)[0]\n",
    "test_eq(torch.isnan(xb).sum(), 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "batch_tfms=[TSStandardize(by_sample=True, by_var=False, verbose=False)]\n",
    "dls = TSDataLoaders.from_dsets(dsets.train, dsets.valid, bs=128, num_workers=0, after_batch=batch_tfms)\n",
    "xb, yb = next(iter(dls.train))\n",
    "test_close(xb.mean(), 0, eps=1e-1)\n",
    "test_close(xb.std(), 1, eps=1e-1)\n",
    "xb, yb = next(iter(dls.valid))\n",
    "test_close(xb.mean(), 0, eps=1e-1)\n",
    "test_close(xb.std(), 1, eps=1e-1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tfms = [None, TSClassification()]\n",
    "batch_tfms = TSStandardize(by_sample=True)\n",
    "dls = get_ts_dls(X, y, splits=splits, tfms=tfms, batch_tfms=batch_tfms, bs=[64, 128], inplace=True)\n",
    "xb, yb = dls.train.one_batch()\n",
    "test_close(xb.mean(), 0, eps=1e-1)\n",
    "test_close(xb.std(), 1, eps=1e-1)\n",
    "xb, yb = dls.valid.one_batch()\n",
    "test_close(xb.mean(), 0, eps=1e-1)\n",
    "test_close(xb.std(), 1, eps=1e-1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tfms = [None, TSClassification()]\n",
    "batch_tfms = TSStandardize(by_sample=True, by_var=False, verbose=False)\n",
    "dls = get_ts_dls(X, y, splits=splits, tfms=tfms, batch_tfms=batch_tfms, bs=[64, 128], inplace=False)\n",
    "xb, yb = dls.train.one_batch()\n",
    "test_close(xb.mean(), 0, eps=1e-1)\n",
    "test_close(xb.std(), 1, eps=1e-1)\n",
    "xb, yb = dls.valid.one_batch()\n",
    "test_close(xb.mean(), 0, eps=1e-1)\n",
    "test_close(xb.std(), 1, eps=1e-1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "\n",
    "@patch\n",
    "def mul_min(x:(torch.Tensor, TSTensor, NumpyTensor), axes=(), keepdim=False):\n",
    "    if axes == (): return retain_type(x.min(), x)\n",
    "    axes = reversed(sorted(axes if is_listy(axes) else [axes]))\n",
    "    min_x = x\n",
    "    for ax in axes: min_x, _ = min_x.min(ax, keepdim)\n",
    "    return retain_type(min_x, x)\n",
    "\n",
    "\n",
    "@patch\n",
    "def mul_max(x:(torch.Tensor, TSTensor, NumpyTensor), axes=(), keepdim=False):\n",
    "    if axes == (): return retain_type(x.max(), x)\n",
    "    axes = reversed(sorted(axes if is_listy(axes) else [axes]))\n",
    "    max_x = x\n",
    "    for ax in axes: max_x, _ = max_x.max(ax, keepdim)\n",
    "    return retain_type(max_x, x)\n",
    "\n",
    "\n",
    "class TSNormalize(Transform):\n",
    "    \"Normalizes batch of type `TSTensor`\"\n",
    "    parameters, order = L('min', 'max'), 90\n",
    "    _setup = True # indicates it requires set up\n",
    "    def __init__(self, min=None, max=None, range=(-1, 1), by_sample=False, by_var=False, by_step=False, clip_values=True, \n",
    "                 use_single_batch=True, verbose=False):\n",
    "        self.min = tensor(min) if min is not None else None\n",
    "        self.max = tensor(max) if max is not None else None\n",
    "        self._setup = (self.min is None and self.max is None) and not by_sample\n",
    "        self.range_min, self.range_max = range\n",
    "        self.by_sample, self.by_var, self.by_step = by_sample, by_var, by_step\n",
    "        drop_axes = []\n",
    "        if by_sample: drop_axes.append(0)\n",
    "        if by_var: drop_axes.append(1)\n",
    "        if by_step: drop_axes.append(2)\n",
    "        self.axes = tuple([ax for ax in (0, 1, 2) if ax not in drop_axes])\n",
    "        if by_var and is_listy(by_var):\n",
    "            self.list_axes = tuple([ax for ax in (0, 1, 2) if ax not in drop_axes]) + (1,)\n",
    "        self.clip_values = clip_values\n",
    "        self.use_single_batch = use_single_batch\n",
    "        self.verbose = verbose\n",
    "        if self.min is not None or self.max is not None:\n",
    "            pv(f'{self.__class__.__name__} min={self.min}, max={self.max}, by_sample={self.by_sample}, by_var={self.by_var}, by_step={self.by_step}\\n', self.verbose)\n",
    "            \n",
    "    @classmethod\n",
    "    def from_stats(cls, min, max, range_min=0, range_max=1): return cls(min, max, self.range_min, self.range_max)\n",
    "\n",
    "    def setups(self, dl: DataLoader):\n",
    "        if self._setup:\n",
    "            if not self.use_single_batch:\n",
    "                o = dl.dataset.__getitem__([slice(None)])[0]\n",
    "            else:\n",
    "                o, *_ = dl.one_batch()\n",
    "            if self.by_var and is_listy(self.by_var):\n",
    "                shape = torch.mean(o, dim=self.axes, keepdim=self.axes!=()).shape\n",
    "                _min = torch.zeros(*shape, device=o.device) + self.range_min\n",
    "                _max = torch.zeros(*shape, device=o.device) + self.range_max\n",
    "                for v in self.by_var:\n",
    "                    if not is_listy(v): v = [v]\n",
    "                    _min[:, v] = o[:, v].mul_min(self.axes if len(v) == 1 else self.list_axes, keepdim=self.axes!=())\n",
    "                    _max[:, v] = o[:, v].mul_max(self.axes if len(v) == 1 else self.list_axes, keepdim=self.axes!=())\n",
    "            else:\n",
    "                _min, _max = o.mul_min(self.axes, keepdim=self.axes!=()), o.mul_max(self.axes, keepdim=self.axes!=())\n",
    "            self.min, self.max = _min, _max\n",
    "            if len(self.min.shape) == 0: \n",
    "                pv(f'{self.__class__.__name__} min={self.min}, max={self.max}, by_sample={self.by_sample}, by_var={self.by_var}, by_step={self.by_step}\\n', \n",
    "                   self.verbose)\n",
    "            else:\n",
    "                pv(f'{self.__class__.__name__} min shape={self.min.shape}, max shape={self.max.shape}, by_sample={self.by_sample}, by_var={self.by_var}, by_step={self.by_step}\\n', \n",
    "                   self.verbose)\n",
    "            self._setup = False\n",
    "        elif self.by_sample: self.min, self.max = -torch.ones(1), torch.ones(1)\n",
    "\n",
    "    def encodes(self, o:TSTensor): \n",
    "        if self.by_sample: \n",
    "            if self.by_var and is_listy(self.by_var):\n",
    "                shape = torch.mean(o, dim=self.axes, keepdim=self.axes!=()).shape\n",
    "                _min = torch.zeros(*shape, device=o.device) + self.range_min\n",
    "                _max = torch.ones(*shape, device=o.device) + self.range_max\n",
    "                for v in self.by_var:\n",
    "                    if not is_listy(v): v = [v]\n",
    "                    _min[:, v] = o[:, v].mul_min(self.axes, keepdim=self.axes!=())\n",
    "                    _max[:, v] = o[:, v].mul_max(self.axes, keepdim=self.axes!=())\n",
    "            else:\n",
    "                _min, _max = o.mul_min(self.axes, keepdim=self.axes!=()), o.mul_max(self.axes, keepdim=self.axes!=())\n",
    "            self.min, self.max = _min, _max\n",
    "        output = ((o - self.min) / (self.max - self.min)) * (self.range_max - self.range_min) + self.range_min\n",
    "        if self.clip_values:\n",
    "            if self.by_var and is_listy(self.by_var):\n",
    "                for v in self.by_var:\n",
    "                    if not is_listy(v): v = [v]\n",
    "                    output[:, v] = torch.clamp(output[:, v], self.range_min, self.range_max)\n",
    "            else:\n",
    "                output = torch.clamp(output, self.range_min, self.range_max)\n",
    "        return output\n",
    "    \n",
    "    def __repr__(self): return f'{self.__class__.__name__}(by_sample={self.by_sample}, by_var={self.by_var}, by_step={self.by_step})'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "batch_tfms = [TSNormalize()]\n",
    "dls = TSDataLoaders.from_dsets(dsets.train, dsets.valid, bs=128, num_workers=0, after_batch=batch_tfms)\n",
    "xb, yb = next(iter(dls.train))\n",
    "assert xb.max() <= 1\n",
    "assert xb.min() >= -1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "batch_tfms=[TSNormalize(by_sample=True, by_var=False, verbose=False)]\n",
    "dls = TSDataLoaders.from_dsets(dsets.train, dsets.valid, bs=128, num_workers=0, after_batch=batch_tfms)\n",
    "xb, yb = next(iter(dls.train))\n",
    "assert xb.max() <= 1\n",
    "assert xb.min() >= -1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "batch_tfms = [TSNormalize(by_var=[0, [1, 2]], use_single_batch=False, clip_values=False, verbose=False)]\n",
    "dls = TSDataLoaders.from_dsets(dsets.train, dsets.valid, bs=128, num_workers=0, after_batch=batch_tfms)\n",
    "xb, yb = next(iter(dls.train))\n",
    "assert xb[:, [0, 1, 2]].max() <= 1\n",
    "assert xb[:, [0, 1, 2]].min() >= -1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class TSClipOutliers(Transform):\n",
    "    \"Clip outliers batch of type `TSTensor` based on the IQR\"\n",
    "    parameters, order = L('min', 'max'), 90\n",
    "    _setup = True # indicates it requires set up\n",
    "    def __init__(self, min=None, max=None, by_sample=False, by_var=False, use_single_batch=False, verbose=False):\n",
    "        \n",
    "        self.min = tensor(min) if min is not None else tensor(-np.inf)\n",
    "        self.max = tensor(max) if max is not None else tensor(np.inf)\n",
    "        self.by_sample, self.by_var = by_sample, by_var\n",
    "        self._setup = (min is None or max is None) and not by_sample \n",
    "        if by_sample and by_var: self.axis = (2)\n",
    "        elif by_sample: self.axis = (1, 2)\n",
    "        elif by_var: self.axis = (0, 2)\n",
    "        else: self.axis = None\n",
    "        self.use_single_batch = use_single_batch\n",
    "        self.verbose = verbose\n",
    "        if min is not None or max is not None:\n",
    "            pv(f'{self.__class__.__name__} min={min}, max={max}\\n', self.verbose)\n",
    "\n",
    "    def setups(self, dl: DataLoader):\n",
    "        if self._setup:\n",
    "            if not self.use_single_batch:\n",
    "                o = dl.dataset.__getitem__([slice(None)])[0]\n",
    "            else:\n",
    "                o, *_ = dl.one_batch()\n",
    "            min, max = get_outliers_IQR(o, self.axis)\n",
    "            self.min, self.max = tensor(min), tensor(max)\n",
    "            if self.axis is None: pv(f'{self.__class__.__name__} min={self.min}, max={self.max}, by_sample={self.by_sample}, by_var={self.by_var}\\n', \n",
    "                                     self.verbose)\n",
    "            else: pv(f'{self.__class__.__name__} min={self.min.shape}, max={self.max.shape}, by_sample={self.by_sample}, by_var={self.by_var}\\n', \n",
    "                     self.verbose)\n",
    "            self._setup = False\n",
    "            \n",
    "    def encodes(self, o:TSTensor):\n",
    "        if self.axis is None: return torch.clamp(o, self.min, self.max)\n",
    "        elif self.by_sample: \n",
    "            min, max = get_outliers_IQR(o, axis=self.axis)\n",
    "            self.min, self.max = o.new(min), o.new(max)\n",
    "        return torch_clamp(o, self.min, self.max)\n",
    "    \n",
    "    def __repr__(self): return f'{self.__class__.__name__}(by_sample={self.by_sample}, by_var={self.by_var})'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "TSClipOutliers min=-1, max=1\n",
      "\n"
     ]
    }
   ],
   "source": [
    "batch_tfms=[TSClipOutliers(-1, 1, verbose=True)]\n",
    "dls = TSDataLoaders.from_dsets(dsets.train, dsets.valid, bs=128, num_workers=0, after_batch=batch_tfms)\n",
    "xb, yb = next(iter(dls.train))\n",
    "assert xb.max() <= 1\n",
    "assert xb.min() >= -1\n",
    "test_close(xb.min(), -1, eps=1e-1)\n",
    "test_close(xb.max(), 1, eps=1e-1)\n",
    "xb, yb = next(iter(dls.valid))\n",
    "test_close(xb.min(), -1, eps=1e-1)\n",
    "test_close(xb.max(), 1, eps=1e-1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# export\n",
    "class TSClip(Transform):\n",
    "    \"Clip  batch of type `TSTensor`\"\n",
    "    parameters, order = L('min', 'max'), 90\n",
    "    def __init__(self, min=-6, max=6): \n",
    "        self.min = torch.tensor(min)\n",
    "        self.max = torch.tensor(max)\n",
    "\n",
    "    def encodes(self, o:TSTensor):\n",
    "        return torch.clamp(o, self.min, self.max)\n",
    "    def __repr__(self): return f'{self.__class__.__name__}(min={self.min}, max={self.max})'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "t = TSTensor(torch.randn(10, 20, 100)*10)\n",
    "test_le(TSClip()(t).max().item(), 6)\n",
    "test_ge(TSClip()(t).min().item(), -6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class TSRobustScale(Transform):\n",
    "    r\"\"\"This Scaler removes the median and scales the data according to the quantile range (defaults to IQR: Interquartile Range)\"\"\"\n",
    "    parameters, order = L('median', 'min', 'max'), 90\n",
    "    _setup = True # indicates it requires set up\n",
    "    def __init__(self, median=None, min=None, max=None, by_sample=False, by_var=False, quantile_range=(25.0, 75.0), use_single_batch=True, verbose=False):\n",
    "        self.median = tensor(median) if median is not None else tensor(0)\n",
    "        self.min = tensor(min) if min is not None else tensor(-np.inf)\n",
    "        self.max = tensor(max) if max is not None else tensor(np.inf)\n",
    "        self._setup = (median is None or min is None or max is None) and not by_sample\n",
    "        self.by_sample, self.by_var = by_sample, by_var\n",
    "        if by_sample and by_var: self.axis = (2)\n",
    "        elif by_sample: self.axis = (1, 2)\n",
    "        elif by_var: self.axis = (0, 2)\n",
    "        else: self.axis = None\n",
    "        self.use_single_batch = use_single_batch\n",
    "        self.verbose = verbose\n",
    "        self.quantile_range = quantile_range\n",
    "        if median is not None or min is not None or max is not None:\n",
    "            pv(f'{self.__class__.__name__} median={median} min={min}, max={max}\\n', self.verbose)\n",
    "\n",
    "    def setups(self, dl: DataLoader):\n",
    "        if self._setup:\n",
    "            if not self.use_single_batch:\n",
    "                o = dl.dataset.__getitem__([slice(None)])[0]\n",
    "            else:\n",
    "                o, *_ = dl.one_batch()\n",
    "            median = get_percentile(o, 50, self.axis)\n",
    "            min, max = get_outliers_IQR(o, self.axis, quantile_range=self.quantile_range)\n",
    "            self.median, self.min, self.max = tensor(median), tensor(min), tensor(max)\n",
    "            if self.axis is None: pv(f'{self.__class__.__name__} median={self.median} min={self.min}, max={self.max}, by_sample={self.by_sample}, by_var={self.by_var}\\n',\n",
    "                                     self.verbose)\n",
    "            else: pv(f'{self.__class__.__name__} median={self.median.shape} min={self.min.shape}, max={self.max.shape}, by_sample={self.by_sample}, by_var={self.by_var}\\n',\n",
    "                     self.verbose)\n",
    "            self._setup = False\n",
    "\n",
    "    def encodes(self, o:TSTensor):\n",
    "        if self.by_sample:\n",
    "            median = get_percentile(o, 50, self.axis)\n",
    "            min, max = get_outliers_IQR(o, axis=self.axis, quantile_range=self.quantile_range)\n",
    "            self.median, self.min, self.max = o.new(median), o.new(min), o.new(max)\n",
    "        return (o - self.median) / (self.max - self.min)\n",
    "\n",
    "    def __repr__(self): return f'{self.__class__.__name__}(by_sample={self.by_sample}, by_var={self.by_var})'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(TSTensor([-0.6113468408584595], device=cpu),\n",
       " TSTensor([1.1915178298950195], device=cpu),\n",
       " TSTensor([-2.7678210735321045], device=cpu),\n",
       " TSTensor([2.461517095565796], device=cpu))"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dls = TSDataLoaders.from_dsets(dsets.train, dsets.valid, num_workers=0)\n",
    "xb, yb = next(iter(dls.train))\n",
    "clipped_xb = TSRobustScale(by_sample=true)(xb)\n",
    "test_ne(clipped_xb, xb)\n",
    "clipped_xb.min(), clipped_xb.max(), xb.min(), xb.max()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class TSDiff(Transform):\n",
    "    \"Differences batch of type `TSTensor`\"\n",
    "    order = 90\n",
    "    def __init__(self, lag=1, pad=True):\n",
    "        self.lag, self.pad = lag, pad\n",
    "\n",
    "    def encodes(self, o:TSTensor): \n",
    "        return torch_diff(o, lag=self.lag, pad=self.pad)\n",
    "    \n",
    "    def __repr__(self): return f'{self.__class__.__name__}(lag={self.lag}, pad={self.pad})'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "t = TSTensor(torch.arange(24).reshape(2,3,4))\n",
    "test_eq(TSDiff()(t)[..., 1:].float().mean(), 1)\n",
    "test_eq(TSDiff(lag=2, pad=False)(t).float().mean(), 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class TSLog(Transform):\n",
    "    \"Log transforms batch of type `TSTensor` + 1. Accepts positive and negative numbers\"\n",
    "    order = 90\n",
    "    def __init__(self, ex=None, **kwargs):\n",
    "        self.ex = ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o:TSTensor):\n",
    "        output = torch.zeros_like(o)\n",
    "        output[o > 0] = torch.log1p(o[o > 0])\n",
    "        output[o < 0] = -torch.log1p(torch.abs(o[o < 0]))\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output\n",
    "    def decodes(self, o:TSTensor):\n",
    "        output = torch.zeros_like(o)\n",
    "        output[o > 0] = torch.exp(o[o > 0]) - 1\n",
    "        output[o < 0] = -torch.exp(torch.abs(o[o < 0])) + 1\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output\n",
    "    def __repr__(self): return f'{self.__class__.__name__}()'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "t = TSTensor(torch.rand(2,3,4)) * 2 - 1 \n",
    "tfm = TSLog()\n",
    "enc_t = tfm(t)\n",
    "test_ne(enc_t, t)\n",
    "test_close(tfm.decodes(enc_t).data, t.data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class TSCyclicalPosition(Transform):\n",
    "    \"\"\"Concatenates the position along the sequence as 2 additional variables (sine and cosine)\n",
    "    \n",
    "        Args:\n",
    "            magnitude: added for compatibility. It's not used.\n",
    "    \"\"\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=None, **kwargs): \n",
    "        super().__init__(**kwargs)\n",
    "\n",
    "    def encodes(self, o: TSTensor): \n",
    "        bs,_,seq_len = o.shape\n",
    "        sin, cos = sincos_encoding(seq_len, device=o.device)\n",
    "        output = torch.cat([o, sin.reshape(1,1,-1).repeat(bs,1,1), cos.reshape(1,1,-1).repeat(bs,1,1)], 1)\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "bs, c_in, seq_len = 1,3,100\n",
    "t = TSTensor(torch.rand(bs, c_in, seq_len))\n",
    "enc_t = TSCyclicalPosition()(t)\n",
    "test_ne(enc_t, t)\n",
    "assert t.shape[1] == enc_t.shape[1] - 2\n",
    "plt.plot(enc_t[0, -2:].cpu().numpy().T)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "\n",
    "class TSLinearPosition(Transform):\n",
    "    \"\"\"Concatenates the position along the sequence as 1 additional variable\n",
    "    \n",
    "        Args:\n",
    "            magnitude: added for compatibility. It's not used.\n",
    "    \"\"\"\n",
    "\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=None, lin_range=(-1,1), **kwargs): \n",
    "        self.lin_range = lin_range\n",
    "        super().__init__(**kwargs)\n",
    "\n",
    "    def encodes(self, o: TSTensor): \n",
    "        bs,_,seq_len = o.shape\n",
    "        lin = linear_encoding(seq_len, device=o.device, lin_range=self.lin_range)\n",
    "        output = torch.cat([o, lin.reshape(1,1,-1).repeat(bs,1,1)], 1)\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "bs, c_in, seq_len = 1,3,100\n",
    "t = TSTensor(torch.rand(bs, c_in, seq_len))\n",
    "enc_t = TSLinearPosition()(t)\n",
    "test_ne(enc_t, t)\n",
    "assert t.shape[1] == enc_t.shape[1] - 1\n",
    "plt.plot(enc_t[0, -1].cpu().numpy().T)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class TSLogReturn(Transform):\n",
    "    \"Calculates log-return of batch of type `TSTensor`. For positive values only\"\n",
    "    order = 90\n",
    "    def __init__(self, lag=1, pad=True):\n",
    "        self.lag, self.pad = lag, pad\n",
    "\n",
    "    def encodes(self, o:TSTensor):\n",
    "        return torch_diff(torch.log(o), lag=self.lag, pad=self.pad)\n",
    "\n",
    "    def __repr__(self): return f'{self.__class__.__name__}(lag={self.lag}, pad={self.pad})'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "t = TSTensor([1,2,4,8,16,32,64,128,256]).float()\n",
    "test_eq(TSLogReturn(pad=False)(t).std(), 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class TSAdd(Transform):\n",
    "    \"Add a defined amount to each batch of type `TSTensor`.\"\n",
    "    order = 90\n",
    "    def __init__(self, add):\n",
    "        self.add = add\n",
    "\n",
    "    def encodes(self, o:TSTensor):\n",
    "        return torch.add(o, self.add)\n",
    "    def __repr__(self): return f'{self.__class__.__name__}(lag={self.lag}, pad={self.pad})'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "t = TSTensor([1,2,3]).float()\n",
    "test_eq(TSAdd(1)(t), TSTensor([2,3,4]).float())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## y transforms"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# export\n",
    "\n",
    "class Preprocessor():\n",
    "    def __init__(self, preprocessor, **kwargs): \n",
    "        self.preprocessor = preprocessor(**kwargs)\n",
    "        \n",
    "    def fit(self, o): \n",
    "        if isinstance(o, pd.Series): o = o.values.reshape(-1,1)\n",
    "        else: o = o.reshape(-1,1)\n",
    "        self.fit_preprocessor = self.preprocessor.fit(o)\n",
    "        return self.fit_preprocessor\n",
    "    \n",
    "    def transform(self, o, copy=True):\n",
    "        if type(o) in [float, int]: o = array([o]).reshape(-1,1)\n",
    "        o_shape = o.shape\n",
    "        if isinstance(o, pd.Series): o = o.values.reshape(-1,1)\n",
    "        else: o = o.reshape(-1,1)\n",
    "        output = self.fit_preprocessor.transform(o).reshape(*o_shape)\n",
    "        if isinstance(o, torch.Tensor): return o.new(output)\n",
    "        return output\n",
    "    \n",
    "    def inverse_transform(self, o, copy=True):\n",
    "        o_shape = o.shape\n",
    "        if isinstance(o, pd.Series): o = o.values.reshape(-1,1)\n",
    "        else: o = o.reshape(-1,1)\n",
    "        output = self.fit_preprocessor.inverse_transform(o).reshape(*o_shape)\n",
    "        if isinstance(o, torch.Tensor): return o.new(output)\n",
    "        return output\n",
    "\n",
    "\n",
    "StandardScaler = partial(sklearn.preprocessing.StandardScaler)\n",
    "setattr(StandardScaler, '__name__', 'StandardScaler')\n",
    "RobustScaler = partial(sklearn.preprocessing.RobustScaler)\n",
    "setattr(RobustScaler, '__name__', 'RobustScaler')\n",
    "Normalizer = partial(sklearn.preprocessing.MinMaxScaler, feature_range=(-1, 1))\n",
    "setattr(Normalizer, '__name__', 'Normalizer')\n",
    "BoxCox = partial(sklearn.preprocessing.PowerTransformer, method='box-cox')\n",
    "setattr(BoxCox, '__name__', 'BoxCox')\n",
    "YeoJohnshon = partial(sklearn.preprocessing.PowerTransformer, method='yeo-johnson')\n",
    "setattr(YeoJohnshon, '__name__', 'YeoJohnshon')\n",
    "Quantile = partial(sklearn.preprocessing.QuantileTransformer, n_quantiles=1_000, output_distribution='normal', random_state=0)\n",
    "setattr(Quantile, '__name__', 'Quantile')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAABAYAAABKCAYAAAAoj1bdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAQG0lEQVR4nO3dfXDV1Z3H8c8nCfKwKBqJIEQ3FiwEUwPGgnYpUNqhan1Yi/gEPlXqjtPZdrVb6e521CJO3Z3O1u7a3fEZdnVZXaCuOu6qWxF8mLIKGkUBoSwWbCKhSUSklFzy3T/uL+1tTALBm9zc3PdrhuH+fr9zz/n+4HDI/d5zzs8RIQAAAAAAUJiKch0AAAAAAADIHRIDAAAAAAAUMBIDAAAAAAAUMBIDAAAAAAAUMBIDAAAAAAAUMBIDAAAAAAAUMBIDAIC8Zft52/OT13NtP/MJ6qqwHbZLkuP/sn1VluL8vO1NGcfbbH8pG3Un9b1le0a26gMAAIWFxAAAIKdsT7X9su0PbDfafsn2Z7tbT0Q8HBGzMuoN22MPN66IODsilhys3KG0ExEvRMS4w42lXXuLbS9qV/8pEfF8NuoHAACFpyTXAQAACpftoyQ9Kel6SY9KOkLS5yX9NpdxZZPtkohI5ToOAACAzjBjAACQS5+WpIhYGhEHIuI3EfFMRLwhSbavTmYQ3JXMKNho+4sdVZSUfTF5vTo5XWt7j+1LOihfbPuHtnfZ3irpK+2uZy5TGGt7VRLDLtuPdNaO7Rm2d9heYLte0oNt59qF8Fnbb9tusv2g7UHt7yMjlkhiuE7SXEk3Je09kVz/3dIE2wNt32n7V8mvO20PTK61xfZt2ztt19m+5qB/SwAAoF8jMQAAyKV3JB2wvcT22baP6aDMFEm/kDRc0i2SVtgu7arSiJiWvKyOiKER8UgHxb4u6VxJkySdLumiLqq8TdIzko6RVC7pHw/SzkhJpZL+WNJ1ndQ5V9KXJY1ROkHyva7uKWnvHkkPS/q7pL3zOij2N5LOkDRRUrWkye3qHilpmKTRkq6V9JNO/twBAECBIDEAAMiZiNgtaaqkkHSvpAbbj9sekVFsp6Q7I6Il+eC9Se2+3T9MFyf1bo+IRkk/6KJsi9If8kdFxL6IeLGLspLUKumWiPhtRPymkzJ3ZbR9u6TLunsDnZgraWFE7IyIBknfl3RFxvWW5HpLRDwlaY+krOx/AAAA8hOJAQBATkXEhoi4OiLKJVVJGiXpzowi70VEZBy/m5T5pEZJ2t6u3s7cJMmS/jd5AsDXDlJ3Q0TsO0iZ9m1n456U1JN5L+3r/nW7PQ/2ShqapbYBAEAeIjEAAOgzImKjpMVKJwjajLbtjOMTJf0qC83VSTqhXb2dxVUfEV+PiFGS/kzSPx3kSQTRxbU27dtuu6ePJA1pu2B7ZDfr/pXSsxs6qhsAAOBjSAwAAHLG9vhkI7zy5PgEpafU/zyj2HGSvml7gO05kiolPXUI1b8v6VNdXH80qbc8WWP/3S7inNMWo6QmpT+ctx5iO535RtJ2qdL7ArTtT1Ar6RTbE5MNCW9t976DtbdU0vdsl9keLulmSQ8dRnwAAKBAkBgAAOTSh0pvLrjG9kdKJwTWS/p2Rpk1kk6WtEvptfgXRcSvD6HuWyUtsd1s++IOrt8r6WmlP4ivk7Sii7o+m8S4R9Ljkr4VEVsPsZ3O/JvSGxpuVXpzxUWSFBHvSFoo6X8kbZbUfj+D+yVNSNp7rIN6F0l6VdIbkt5M7m1RN+ICAAAFxn+4bBMAgL7D9tWS5kfE1FzHAgAA0F8xYwAAAAAAgAJGYgAAAAAAgALGUgIAAAAAAAoYMwYAAAAAAChgJAYAAAAAAChgJT1RqT08pIqeqBoAAADIS0MqN+Q6BCAr9m7YuysiynIdB7KnRxID6aTAqz1TNQAAAJCHxj9Uk+sQgKxYV7Pu3VzHgOxiKQEAAAAAAAWMxAAAAAAAAAWMxAAAAAAAAAWsh/YYAAAAAACg71q7du1xJSUl90mqUv/+0rxV0vpUKjW/pqZmZ0cFSAwAAAAAAApOSUnJfSNHjqwsKytrKioqilzH01NaW1vd0NAwob6+/j5J53dUpj9nRQAAAAAA6ExVWVnZ7v6cFJCkoqKiKCsr+0DpmREdl+nFeAAAAAAA6CuK+ntSoE1yn51+/mcpAQAAAAAAvay+vr54xowZ4yRp165dA4qKiqK0tDQlSa+//vqGQYMGdZq0WL169ZAHHnjg2MWLF2/PRiwHTQzYfkDSuZJ2RkSnUw8AAAAAAMhXtmqyWV+E1nZ1feTIkQc2btz4tiTdeOONo4YOHXpg4cKF77ddb2lp0YABAzp877Rp0/ZOmzZtb7ZiPZSlBIslnZWtBgEAAAAAwMfNnj274vLLLz/x1FNPHX/99deXr1y5csjEiRPHV1ZWTpg0adL42tragZL05JNPHvmFL3xhrJROKsyZM6di8uTJ48rLyz+zaNGi47rb7kFnDETEatsV3b4jAAAAAADQLXV1dUesW7duY0lJiRobG4teeeWVjQMGDNBjjz125E033VT+9NNP/6L9e7Zs2TLo5Zdf3tTc3FxcWVlZ9Z3vfKdh4MCBh7x/Qtb2GLB9naTr0kcnZqtaAAAAAAAKxle/+tWmkpL0R/XGxsbiSy655KRt27YNsh0tLS3u6D2zZs1qHjx4cAwePDhVWlrasmPHjpIxY8a0HGqbWXsqQUTcExGnR8TpUlm2qgUAAAAAoGAMHTq0te31ggULRk+fPv3DzZs3v/XEE09s2b9/f4ef4TNnBxQXFyuVSnWYQOgMjysEAAAAAKAP2r17d3F5efl+Sbr77ruH91Q7JAYAAAAAAOiDFixYUH/rrbeWV1ZWTkilUj3WjiO63o/A9lJJMyQNl/S+pFsi4v6u33N6SK9mK0YAAAAg7522NqtPQgNyZl3NurXpJeT5rba2dlt1dfWuXMfRW2pra4dXV1dXdHTtUJ5KcFnWIwIAAAAAAH0CSwkAAAAAAChgJAYAAAAAAChgJAYAAAAAAChgJAYAAAAAAChgJAYAAAAAAChgJAYAAAAAAOhlU6ZM+fTy5cuPyjy3cOHC4+bOnXtiR+UnT548bvXq1UMkafr06WN37dpV3L7MjTfeOOrmm28e0d1YDvq4QgAAAAAA+ruadTU12axv7Wlr13Z1fc6cOY1Lly4tnT179u62c8uXLy+94447dhys7lWrVm3JRoxtmDEAAAAAAEAvu+KKK5qee+65Yfv27bMkbdq06YidO3cOeOihh0qrqqoqx44de8oNN9wwqqP3jh49+jN1dXUlkrRgwYKRFRUVVTU1NeM2b9488HBi6aEZA2v3SN7UM3UDvWa4pF25DgLIAvoy+gP6MfLeuvR3kfRl9Afjch1AfzBixIgD1dXVHy1btmzYvHnzmpcsWVJ63nnnNd122211I0aMOJBKpfS5z31u3Jo1awZPmTLlNx3V8cILLwz56U9/Wvrmm2++3dLSookTJ06YNGnS3u7G0lNLCTZFxOk9VDfQK2y/Sj9Gf0BfRn9AP0Z/QV9Gf2D71VzH0F9cfPHFjY888sgx8+bNa16xYkXpvffeu23JkiWlixcvHp5KpdzQ0DCgtrZ2UGeJgZUrVw4955xzmo888shWSZo1a1bz4cTBUgIAAAAAAHLg8ssvb37ppZeOevHFF4fs27evqKysLHXXXXeNWLVq1TvvvPPO2zNnzvxg3759Pf65ncQAAAAAAAA5MGzYsNYzzzzzw/nz51dceOGFjU1NTcWDBw9uLS0tPbB9+/aS559/flhX7585c+aep5566ug9e/a4qamp6Nlnnz36cOLoqaUE9/RQvUBvoh+jv6Avoz+gH6O/oC+jP6AfZ9Gll17aeOWVV45ZunTp1kmTJu2rqqraO2bMmKrjjz9+f01NzZ6u3jt16tS9F154YWNVVdUpxx57bMupp5760eHE4Ig4vOgBAAAAAMhTtbW126qrqwtmM9Da2trh1dXVFR1dYykBAAAAAAAFLKuJAdtn2d5ke4vt72azbiCbbJ9ge6Xtt22/ZftbyflS28/a3pz8fkxy3rb/Ienbb9g+Lbd3APwh28W2X7P9ZHJ8ku01SZ99xPYRyfmByfGW5HpFTgMHMtg+2vYy2xttb7B9JuMy8o3tG5KfLdbbXmp7EGMy8oHtB2zvtL0+41y3x2DbVyXlN9u+Khf3gu7LWmLAdrGkn0g6W9IESZfZnpCt+oEsS0n6dkRMkHSGpG8k/fW7kn4WESdL+llyLKX79cnJr+sk/XPvhwx06VuSNmQc/62kH0XEWElNkq5Nzl8rqSk5/6OkHNBX/FjSf0fEeEnVSvdpxmXkDdujJX1T0ukRUSWpWNKlYkxGflgs6ax257o1BtsulXSLpCmSJku6pS2ZgL4tmzMGJkvaEhFbI2K/pH+XdEEW6weyJiLqImJd8vpDpX/4HK10n12SFFsi6U+T1xdI+pdI+7mko20f37tRAx2zXS7pK5LuS44taaakZUmR9n25rY8vk/TFpDyQU7aHSZom6X5Jioj9EdEsxmXknxJJg22XSBoiqU6MycgDEbFaUmO7090dg78s6dmIaIyIJknP6uPJhr6ktbW1tSD+zSX32drZ9WwmBkZL2p5xvCM5B/RpybS9SZLWSBoREXXJpXpJI5LX9G/0ZXdKukm/H+yPldQcEankOLO//q4vJ9c/SMoDuXaSpAZJDybLYu6z/UdiXEYeiYj3JP1Q0i+VTgh8IGmtGJORv7o7Bufb2Ly+oaFhWH9PDrS2trqhoWGYpPWdlempxxUCecH2UEnLJf1FROzOTNJHRNjmsR3o02yfK2lnRKy1PSPH4QCfRImk0yT9eUSssf1j/X7KqiTGZfR9yZTpC5ROdDVL+g/17W9LgUPWH8fgVCo1v76+/r76+voq9e+N+VslrU+lUvM7K5DNxMB7kk7IOC5PzgF9ku0BSicFHo6IFcnp920fHxF1yXSoncl5+jf6qj+RdL7tcyQNknSU0uu0j7ZdknwDldlf2/ryjmSa6zBJv+79sIGP2SFpR0SsSY6XKZ0YYFxGPvmSpP+LiAZJsr1C6XGaMRn5qrtj8HuSZrQ7/3wvxHlYampqdko6P9dx9AXZzIq8IunkZNfVI5TeaOXxLNYPZE2yfu9+SRsi4u8zLj0uqW331Ksk/WfG+SuTHVjPkPRBxrQqIGci4q8iojwiKpQed5+LiLmSVkq6KCnWvi+39fGLkvL9KvuP/BQR9ZK22x6XnPqipLfFuIz88ktJZ9gekvys0daPGZORr7o7Bj8taZbtY5IZNLOSc+jjnM2xJ/nG6k6ld2B9ICJuz1rlQBbZnirpBUlv6vfrsv9a6X0GHpV0oqR3JV0cEY3Jf+53KT0dcK+kayLi1V4PHOhCspTgLyPiXNufUnoT2FJJr0maFxG/tT1I0r8qva9Go6RLI2JrjkIG/oDtiUpvonmEpK2SrlH6SwzGZeQN29+XdInST0B6TdJ8pddYMyajT7O9VOlv+4dLel/ppws8pm6Owba/pvTP1ZJ0e0Q82Iu3gcOU1cQAAAAAAADIL/15gwUAAAAAAHAQJAYAAAAAAChgJAYAAAAAAChgJAYAAAAAAChgJAYAAAAAAChgJAYAAAAAAChgJAYAAAAAAChgJAYAAAAAAChg/w8jbnPbyCehSwAAAABJRU5ErkJggg==\n",
      "text/plain": [
       "<Figure size 1152x36 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Standardize\n",
    "from tsai.data.validation import TimeSplitter\n",
    "y = random_shuffle(np.random.randn(1000) * 10 + 5)\n",
    "splits = TimeSplitter()(y)\n",
    "preprocessor = Preprocessor(StandardScaler)\n",
    "preprocessor.fit(y[splits[0]])\n",
    "y_tfm = preprocessor.transform(y)\n",
    "test_close(preprocessor.inverse_transform(y_tfm), y)\n",
    "plt.hist(y, 50, label='ori',)\n",
    "plt.hist(y_tfm, 50, label='tfm')\n",
    "plt.legend(loc='best')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 1152x36 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# RobustScaler\n",
    "y = random_shuffle(np.random.randn(1000) * 10 + 5)\n",
    "splits = TimeSplitter()(y)\n",
    "preprocessor = Preprocessor(RobustScaler)\n",
    "preprocessor.fit(y[splits[0]])\n",
    "y_tfm = preprocessor.transform(y)\n",
    "test_close(preprocessor.inverse_transform(y_tfm), y)\n",
    "plt.hist(y, 50, label='ori',)\n",
    "plt.hist(y_tfm, 50, label='tfm')\n",
    "plt.legend(loc='best')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 1152x36 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Normalize\n",
    "y = random_shuffle(np.random.rand(1000) * 3 + .5)\n",
    "splits = TimeSplitter()(y)\n",
    "preprocessor = Preprocessor(Normalizer)\n",
    "preprocessor.fit(y[splits[0]])\n",
    "y_tfm = preprocessor.transform(y)\n",
    "test_close(preprocessor.inverse_transform(y_tfm), y)\n",
    "plt.hist(y, 50, label='ori',)\n",
    "plt.hist(y_tfm, 50, label='tfm')\n",
    "plt.legend(loc='best')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 1152x36 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# BoxCox\n",
    "y = random_shuffle(np.random.rand(1000) * 10 + 5)\n",
    "splits = TimeSplitter()(y)\n",
    "preprocessor = Preprocessor(BoxCox)\n",
    "preprocessor.fit(y[splits[0]])\n",
    "y_tfm = preprocessor.transform(y)\n",
    "test_close(preprocessor.inverse_transform(y_tfm), y)\n",
    "plt.hist(y, 50, label='ori',)\n",
    "plt.hist(y_tfm, 50, label='tfm')\n",
    "plt.legend(loc='best')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 1152x36 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# YeoJohnshon\n",
    "y = random_shuffle(np.random.randn(1000) * 10 + 5)\n",
    "y = np.random.beta(.5, .5, size=1000)\n",
    "splits = TimeSplitter()(y)\n",
    "preprocessor = Preprocessor(YeoJohnshon)\n",
    "preprocessor.fit(y[splits[0]])\n",
    "y_tfm = preprocessor.transform(y)\n",
    "test_close(preprocessor.inverse_transform(y_tfm), y)\n",
    "plt.hist(y, 50, label='ori',)\n",
    "plt.hist(y_tfm, 50, label='tfm')\n",
    "plt.legend(loc='best')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 1152x36 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# QuantileTransformer\n",
    "y = - np.random.beta(1, .5, 10000) * 10\n",
    "splits = TimeSplitter()(y)\n",
    "preprocessor = Preprocessor(Quantile)\n",
    "preprocessor.fit(y[splits[0]])\n",
    "plt.hist(y, 50, label='ori',)\n",
    "y_tfm = preprocessor.transform(y)\n",
    "plt.legend(loc='best')\n",
    "plt.show()\n",
    "plt.hist(y_tfm, 50, label='tfm')\n",
    "plt.legend(loc='best')\n",
    "plt.show()\n",
    "test_close(preprocessor.inverse_transform(y_tfm), y, 1e-1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def ReLabeler(cm):\n",
    "    r\"\"\"Changes the labels in a dataset based on a dictionary (class mapping) \n",
    "        Args:\n",
    "            cm = class mapping dictionary\n",
    "    \"\"\"\n",
    "    def _relabel(y):\n",
    "        obj = len(set([len(listify(v)) for v in cm.values()])) > 1\n",
    "        keys = cm.keys()\n",
    "        if obj: \n",
    "            new_cm = {k:v for k,v in zip(keys, [listify(v) for v in cm.values()])}\n",
    "            return np.array([new_cm[yi] if yi in keys else listify(yi) for yi in y], dtype=object).reshape(*y.shape)\n",
    "        else: \n",
    "            new_cm = {k:v for k,v in zip(keys, [listify(v) for v in cm.values()])}\n",
    "            return np.array([new_cm[yi] if yi in keys else listify(yi) for yi in y]).reshape(*y.shape)\n",
    "    return _relabel"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array(['d', 'd', 'b', 'c', 'e', 'c', 'b', 'e', 'd', 'e', 'c', 'c', 'c',\n",
       "        'a', 'b', 'e', 'c', 'e', 'a', 'a'], dtype='<U1'),\n",
       " array(['z', 'z', 'x', 'y', 'z', 'y', 'x', 'z', 'z', 'z', 'y', 'y', 'y',\n",
       "        'x', 'x', 'z', 'y', 'z', 'x', 'x'], dtype='<U1'))"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vals = {0:'a', 1:'b', 2:'c', 3:'d', 4:'e'}\n",
    "y = np.array([vals[i] for i in np.random.randint(0, 5, 20)])\n",
    "labeler = ReLabeler(dict(a='x', b='x', c='y', d='z', e='z'))\n",
    "y_new = labeler(y)\n",
    "test_eq(y.shape, y_new.shape)\n",
    "y, y_new"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#hide\n",
    "from tsai.imports import create_scripts\n",
    "from tsai.export import get_nb_name\n",
    "nb_name = get_nb_name()\n",
    "create_scripts(nb_name);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
